
import java.io.*;
import java.net.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;


/**
 *
 * DARSI is a Distributed Anti-RSI (Repetitive Strain Injury) application
 * designed to coordinate breaks between group members. These short breaks help
 * to prevent Occupational Overuse Syndrome (OOS) and RSI.
 *
 * DARSI differs from other RSI prevention applications in that it manages
 * breaks for small groups by coordinating users and asking them to vote on
 * break times.
 *
 * The process tasks are:
 * <ul>
 * <li>Work</li>
 * <li>Vote</li>
 * <li>Break</li>
 * </ul>
 */
public class Darsi implements TimerCallback, MulticastCallback {

   /**
    * Multicast address used by DARSI.
    */
   public static String DARSI_MULTI_ADDR = "224.0.55.55";

   /**
    * Multicast port used by DARSI.
    */
   public static int DARSI_PORT = 5112;

   /**
    * Default amount of work time (in minutes).
    */
   public static final int DARSI_DEFAULT_WAIT_MINS = 50; //mins

   /**
    * Default amount of time (in minutes) for all clients to vote.
    */
   public static final int DARSI_DEFAULT_VOTE_MINS = 1; //mins

   /**
    * Default amount of break time (in minutes).
    */
   public static final int DARSI_DEFAULT_BREAK_MINS = 10; //mins
   

   /**
    * Listens for multicast traffic on DARSI_MULTI_ADDR:DARSI_PORT.
    */
   private MulticastListener multiListener = null;

   /**
    * Timer for <i>work, vote, break</i> process.
    */
   private TimeNotifier timeNotifier = null;

   /**
    * Image for DARSI.
    */
   //public static final Image image = Toolkit.getDefaultToolkit().getImage("imgs/icon.gif");
   public static final Image image =
      Toolkit.getDefaultToolkit().getImage("imgs/icon.gif");

   /**
    * GUI frame for DARSI.  Mostly unused except to have a dock icon and
    * toolbar.
    */
   private JFrame frame = new JFrame("DARSI");

   /**
    * Progress GUI.
    */
   private ProgressGUI proggui = null;
   
   public static final void main( String[] args ) {
      Darsi darsi = new Darsi();
   }

   /**
    * Default constructor: Creates a new instance of DARSI.
    */
   public Darsi() {
      init();
      restart();
   }

   /**
    * Initializes this DARSI instance.
    */
   private void init() {
      if( SystemTray.isSupported() ) {
         proggui = new SystemTrayProgGUI(
               DARSI_DEFAULT_WAIT_MINS,
               DARSI_DEFAULT_BREAK_MINS,
               DARSI_DEFAULT_VOTE_MINS );
      } else {
         System.out.println( "Frame Menu GUI" );
         proggui = new FrameMenuProgGUI(
               DARSI_DEFAULT_WAIT_MINS,
               DARSI_DEFAULT_BREAK_MINS,
               DARSI_DEFAULT_VOTE_MINS );
      }
      try {
         multiListener = new MulticastListener();
         multiListener.addCallback( this );
         multiListener.start();
      } catch( Exception e ) {
         e.printStackTrace();
      }
   }

   /**
    * (Re)Starts this DARSI instance.
    */
   private void restart() {
      try {
         proggui.setProgress( ProgressGUI.WORK_STAGE, 0 );
         timeNotifier = new TimeNotifier( this ); 
         timeNotifier.schedule( DARSI_DEFAULT_WAIT_MINS );
      } catch( Exception e ) {
         e.printStackTrace();
      }
   }

   /**
    * Timer callback, must be implemented for TimerCallback and gets called
    * once a scheduled job completes.
    */
   public void timer() {
      p = 0;
      if( multiListener != null ) {
         multiListener.send( "TIMER" );
      }
   }

   /**
    * Timer progress.
    */
   private int p = 0;
   public void progress() {
      p++;
      proggui.incrementProgress();
   }

   /**
    * MulticastListener callback, must be implemented for MulticastCallback and
    * gets called on incoming multicast traffic.
    */
   public void handleIncoming( Event event ) {
      if( event.getType() == Event.EVENT_OTHER_HOST_TIMER ) {
         System.out.println( "Other host timer" );
         timeNotifier.cancel();
         timeNotifier = new TimeNotifier( this );
         initVoting();
         showDialog();
      } else if ( event.getType() == Event.EVENT_VOTE ) {
         System.out.println( "Vote: " + event.getAddr() + ", " + event.getPort() );
         if( event.getPort() == 1 ) {
            postpones++;
         } else if( event.getPort() == 2 ) {
            breaks++;
         }
      }
   }

   /**
    * Counter for the total number of incoming votes (this voting round).
    */
   private int totalVotes = 0;

   /**
    * Counter for the number of "Postpone Break" votes (this voting round).
    */
   private int postpones = 0;

   /**
    * Counter for the number of "Take a Break" votes (this voting round).
    */
   private int breaks = 0;

   /**
    * Initializes this DARSI instance for a voting round.
    */
   private void initVoting() {
      proggui.setProgress( ProgressGUI.VOTE_STAGE, 0 );
      totalVotes = 0;
      postpones = 0;
      breaks = 0;
      myVote = -1;
      java.util.Timer t = new java.util.Timer();
      t.schedule( new TimerTask() {
         public void run() {
            stopVoting();
         }
      }, DARSI_DEFAULT_VOTE_MINS * 60000 );
   }

   /**
    * Ends the voting round
    */
   private void stopVoting() {
      System.out.println( "Stop Voting" );
      if( postpones >= breaks ) {
         postpone();
      } else {
         takeBreak();
      }
   }

   /**
    * Postpones a break
    */
   private void postpone() {
      System.out.println( "Postpone" );
      restart();
   }
   
   /**
    * Dialog for break-time
    */
   private JDialog dialog = null;

   /**
    * Initializes the work-break.
    */
   private void takeBreak() {
      System.out.println( "Take a Break!" );
      proggui.setProgress( ProgressGUI.BREAK_STAGE, 0 );
      dialog = new JDialog( frame, "Take a Break!" );
      dialog.setLayout( new BorderLayout() );
      JLabel label = new JLabel( "Take a break for " + DARSI_DEFAULT_BREAK_MINS + " mins." );
      dialog.add( label, BorderLayout.NORTH );
      JProgressBar progBar = new JProgressBar( 0, DARSI_DEFAULT_BREAK_MINS*100 );
      dialog.add( progBar );
      JButton hideButton = new JButton( "Hide" );
      hideButton.addActionListener( new HideButtonActionListener() );
      dialog.add( hideButton, BorderLayout.SOUTH );
      dialog.pack();
      dialog.show();
      for( int i=0; i<DARSI_DEFAULT_BREAK_MINS*100; i++ ) {
         progBar.setValue( i );
         try {
            Thread.sleep( (((DARSI_DEFAULT_BREAK_MINS*100)/60)*1000) );
         } catch ( Exception e ) {
            e.printStackTrace();
         }
      }
      dialog.hide();
      restart();
   }

   /**
    * Action listener to hide/skip the current work-break.
    */
   class HideButtonActionListener implements ActionListener {
      public void actionPerformed( ActionEvent e ) {
         dialog.hide();
      }
   }

   /**
    * container for my current/last vote
    */
   private int myVote = -1;

   /**
    * Shows the voting dialog.
    */
   private void showDialog() {
      //Custom button text
      Object[] options = {"Skip",
         "Postpone",
         "Take Break"};
      myVote = JOptionPane.showOptionDialog(frame,
            "Would you like to take a break now?",
            "DARSI: Break Vote",
            JOptionPane.YES_NO_CANCEL_OPTION,
            JOptionPane.QUESTION_MESSAGE,
            null,
            options,
            options[2]);
      try {
         multiListener.send( InetAddress.getLocalHost().getHostAddress()+","+myVote );
      } catch ( Exception e ) {
         e.printStackTrace();
      }
   }

}
